/*
 * Linux cfg80211 driver - Dongle Host Driver (DHD) related
 *
 * Copyright (C) 1999-2019, Broadcom.
 *
 *      Unless you and Broadcom execute a separate written software license
 * agreement governing use of this software, this software is licensed to you
 * under the terms of the GNU General Public License version 2 (the "GPL"),
 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 * following added to such license:
 *
 *      As a special exception, the copyright holders of this software give you
 * permission to link this software with independent modules, and to copy and
 * distribute the resulting executable under terms of your choice, provided that
 * you also meet, for each linked independent module, the terms and conditions
 * of the license of that module.  An independent module is a module which is
 * not derived from this software.  The special exception does not apply to any
 * modifications of the software.
 *
 *      Notwithstanding the above, under no circumstances may you combine this
 * software in any way with any other Broadcom software provided under a license
 * other than the GPL, without Broadcom's express prior written consent.
 *
 *
 * <<Broadcom-WL-IPTag/Open:>>
 *
 * $Id: dhd_cfg80211.c 807961 2019-03-05 05:47:47Z $
 */

#include <linux/vmalloc.h>
#include <net/rtnetlink.h>

#include <bcmutils.h>
#include <wldev_common.h>
#include <wl_cfg80211.h>
#include <dhd_cfg80211.h>

#ifdef PKT_FILTER_SUPPORT
#include <dngl_stats.h>
#include <dhd.h>
#endif // endif

#ifdef PKT_FILTER_SUPPORT
extern uint dhd_pkt_filter_enable;
extern uint dhd_master_mode;
extern void dhd_pktfilter_offload_enable(dhd_pub_t *dhd, char *arg, int enable,
                                         int master_mode);
#endif // endif

static int dhd_dongle_up = FALSE;

#include <dngl_stats.h>
#include <dhd.h>
#include <dhdioctl.h>
#include <wlioctl.h>
#include <brcm_nl80211.h>
#include <dhd_cfg80211.h>

#ifdef CONFIG_AP6XXX_WIFI6_HDF
#include "net_device.h"
struct NetDevice *GetHdfNetDeviceByLinuxInf(struct net_device *dev);

#endif

static s32 wl_dongle_up(struct net_device *ndev);
static s32 wl_dongle_down(struct net_device *ndev);

/**
 * Function implementations
 */

s32 dhd_cfg80211_init(struct bcm_cfg80211 *cfg)
{
    dhd_dongle_up = FALSE;
    return 0;
}

s32 dhd_cfg80211_deinit(struct bcm_cfg80211 *cfg)
{
    dhd_dongle_up = FALSE;
    return 0;
}

s32 dhd_cfg80211_down(struct bcm_cfg80211 *cfg)
{
    struct net_device *ndev;
    s32 err = 0;
    dhd_pub_t *dhd = (dhd_pub_t *)(cfg->pub);

    WL_TRACE(("In\n"));
    if ((!dhd_dongle_up) || (!dhd->up)) {
        WL_INFORM_MEM(("Dongle is already down\n"));
        err = 0;
        goto done;
    }
    ndev = bcmcfg_to_prmry_ndev(cfg);
    wl_dongle_down(ndev);
done:
    dhd_dongle_up = FALSE;
    return err;
}

s32 dhd_cfg80211_set_p2p_info(struct bcm_cfg80211 *cfg, int val)
{
    dhd_pub_t *dhd = (dhd_pub_t *)(cfg->pub);
    dhd->op_mode |= val;
    WL_ERR(("Set : op_mode=0x%04x\n", dhd->op_mode));
#ifdef ARP_OFFLOAD_SUPPORT
    if (dhd->arp_version == 1) {
        /* IF P2P is enabled, disable arpoe */
        dhd_arp_offload_set(dhd, 0);
        dhd_arp_offload_enable(dhd, false);
    }
#endif /* ARP_OFFLOAD_SUPPORT */

    return 0;
}

s32 dhd_cfg80211_clean_p2p_info(struct bcm_cfg80211 *cfg)
{
    dhd_pub_t *dhd = (dhd_pub_t *)(cfg->pub);
    dhd->op_mode &= ~(DHD_FLAG_P2P_GC_MODE | DHD_FLAG_P2P_GO_MODE);
    WL_ERR(("Clean : op_mode=0x%04x\n", dhd->op_mode));

#ifdef ARP_OFFLOAD_SUPPORT
    if (dhd->arp_version == 1) {
        /* IF P2P is disabled, enable arpoe back for STA mode. */
        dhd_arp_offload_set(dhd, dhd_arp_mode);
        dhd_arp_offload_enable(dhd, true);
    }
#endif /* ARP_OFFLOAD_SUPPORT */

    return 0;
}

#ifdef WL_STATIC_IF
int32 wl_cfg80211_update_iflist_info(struct bcm_cfg80211 *cfg,
                                     struct net_device *ndev, int ifidx,
                                     uint8 *addr, int bssidx, char *name,
                                     int if_state)
{
    return dhd_update_iflist_info(cfg->pub, ndev, ifidx, addr, bssidx, name,
                                  if_state);
}
#endif /* WL_STATIC_IF */

struct net_device *wl_cfg80211_allocate_if(struct bcm_cfg80211 *cfg, int ifidx,
                                           const char *name, uint8 *mac,
                                           uint8 bssidx, const char *dngl_name)
{
    return dhd_allocate_if(cfg->pub, ifidx, name, mac, bssidx, FALSE,
                           dngl_name);
}

int wl_cfg80211_register_if(struct bcm_cfg80211 *cfg, int ifidx,
                            struct net_device *ndev, bool rtnl_lock_reqd)
{
    return dhd_register_if(cfg->pub, ifidx, rtnl_lock_reqd);
}

int wl_cfg80211_remove_if(struct bcm_cfg80211 *cfg, int ifidx,
                          struct net_device *ndev, bool rtnl_lock_reqd)
{
    WL_ERR(("lijg123: call wl_cfg80211_remove_if ifidx=%d rtnl_lock_reqd=%d\n",
            ifidx, rtnl_lock_reqd));
    return dhd_remove_if(cfg->pub, ifidx, rtnl_lock_reqd);
}

void wl_cfg80211_cleanup_if(struct net_device *net)
{
    struct bcm_cfg80211 *cfg = wl_get_cfg(net);
    BCM_REFERENCE(cfg);
    dhd_cleanup_if(net);
}

struct net_device *dhd_cfg80211_netdev_free(struct net_device *ndev)
{
    struct bcm_cfg80211 *cfg;
#ifdef CONFIG_AP6XXX_WIFI6_HDF
    struct NetDevice *hnetdev = NULL;
#endif

    if (ndev) {
        cfg = wl_get_cfg(ndev);
        if (ndev->ieee80211_ptr) {
            MFREE(cfg->osh, ndev->ieee80211_ptr, sizeof(struct wireless_dev));
            ndev->ieee80211_ptr = NULL;
        }

        WL_ERR(("call dhd_cfg80211_netdev_free %s\n", ndev->name));
#ifdef CONFIG_AP6XXX_WIFI6_HDF
        hnetdev = GetHdfNetDeviceByLinuxInf(ndev);
        if (hnetdev == NULL) {
            WL_ERR(("get NetDevice %s failed\n", ndev->name));
            return NULL;
        }

        // clear private obj
        kfree(hnetdev->mlPriv);
        hnetdev->mlPriv = NULL;

        // clear NetDevice object
        NetDeviceDeInit(hnetdev);
#else
        free_netdev(ndev);
#endif
        return NULL;
    }

    return ndev;
}

void dhd_netdev_free(struct net_device *ndev)
{
#ifdef WL_CFG80211
    WL_ERR(("lijg123 call dhd_netdev_free 01 run this %s\n", ndev->name));
    ndev = dhd_cfg80211_netdev_free(ndev);
#endif // endif
    if (ndev) {
        WL_ERR(("lijg123 call dhd_netdev_free 02 %s\n", ndev->name));
        free_netdev(ndev);
    }
}

static s32 wl_dongle_up(struct net_device *ndev)
{
    s32 err = 0;
    u32 local_up = 0;

    err = wldev_ioctl_set(ndev, WLC_UP, &local_up, sizeof(local_up));
    if (unlikely(err)) {
        WL_ERR(("WLC_UP error (%d)\n", err));
    }
    return err;
}

static s32 wl_dongle_down(struct net_device *ndev)
{
    s32 err = 0;
    u32 local_down = 0;

    err = wldev_ioctl_set(ndev, WLC_DOWN, &local_down, sizeof(local_down));
    if (unlikely(err)) {
        WL_ERR(("WLC_DOWN error (%d)\n", err));
    }
    return err;
}

s32 wl_dongle_roam(struct net_device *ndev, u32 roamvar, u32 bcn_timeout)
{
    s32 err = 0;

    /* Setup timeout if Beacons are lost and roam is off to report link down */
    if (roamvar) {
        err = wldev_iovar_setint(ndev, "bcn_timeout", bcn_timeout);
        if (unlikely(err)) {
            WL_ERR(("bcn_timeout error (%d)\n", err));
            goto dongle_rom_out;
        }
    }
    /* Enable/Disable built-in roaming to allow supplicant to take care of
     * roaming */
    err = wldev_iovar_setint(ndev, "roam_off", roamvar);
    if (unlikely(err)) {
        WL_ERR(("roam_off error (%d)\n", err));
        goto dongle_rom_out;
    }
dongle_rom_out:
    return err;
}

s32 dhd_config_dongle(struct bcm_cfg80211 *cfg)
{
#ifndef DHD_SDALIGN
#define DHD_SDALIGN 32
#endif // endif
    struct net_device *ndev;
    s32 err = 0;

    WL_TRACE(("In\n"));
    if (dhd_dongle_up) {
        WL_ERR(("Dongle is already up\n"));
        return err;
    }

    ndev = bcmcfg_to_prmry_ndev(cfg);

    err = wl_dongle_up(ndev);
    if (unlikely(err)) {
        WL_ERR(("wl_dongle_up failed\n"));
        goto default_conf_out;
    }
    dhd_dongle_up = true;

default_conf_out:

    return err;
}

int dhd_cfgvendor_priv_string_handler(struct bcm_cfg80211 *cfg,
                                      struct wireless_dev *wdev,
                                      const struct bcm_nlmsg_hdr *nlioc,
                                      void *buf)
{
    struct net_device *ndev = NULL;
    dhd_pub_t *dhd;
    dhd_ioctl_t ioc = {0, NULL, 0, 0, 0, 0, 0};
    int ret = 0;
    int8 index;

    WL_TRACE(("entry: cmd = %d\n", nlioc->cmd));

    dhd = cfg->pub;
    DHD_OS_WAKE_LOCK(dhd);

    ndev = wdev_to_wlc_ndev(wdev, cfg);
    index = dhd_net2idx(dhd->info, ndev);
    if (index == DHD_BAD_IF) {
        WL_ERR(("Bad ifidx from wdev:%p\n", wdev));
        ret = BCME_ERROR;
        goto done;
    }

    ioc.cmd = nlioc->cmd;
    ioc.len = nlioc->len;
    ioc.set = nlioc->set;
    ioc.driver = nlioc->magic;
    ioc.buf = buf;
    ret = dhd_ioctl_process(dhd, index, &ioc, buf);
    if (ret) {
        WL_TRACE(("dhd_ioctl_process return err %d\n", ret));
        ret = OSL_ERROR(ret);
        goto done;
    }

done:
    DHD_OS_WAKE_UNLOCK(dhd);
    return ret;
}
